package bloggo

import (
	"bloggo/structs"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/chunanyong/zorm"
	"golang.org/x/crypto/bcrypt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

type tokenDTO struct {
	AccessToken  string `json:"access_token"`
	ExpiresIn    string `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
}

type openDTO struct {
	ClientId    string `json:"client_id"`
	Openid      string `json:"openid"`
	Unionid     string `json:"unionid"`
	AccessToken string `json:"access_token"`
}

type qqUserInfo struct {
	Ret             int    `json:"ret"`
	Msg             string `json:"msg"`
	IsLost          int    `json:"is_lost"`
	Nickname        string `json:"nickname"`
	Gender          string `json:"gender"`
	GenderType      int    `json:"gender_type"`
	Province        string `json:"province"`
	City            string `json:"city"`
	Year            string `json:"year"`
	Constellation   string `json:"constellation"`
	Figureurl       string `json:"figureurl"`
	Figureurl1      string `json:"figureurl_1"`
	Figureurl2      string `json:"figureurl_2"`
	FigureurlQq1    string `json:"figureurl_qq_1"`
	FigureurlQq2    string `json:"figureurl_qq_2"`
	FigureurlQq     string `json:"figureurl_qq"`
	FigureurlType   string `json:"figureurl_type"`
	IsYellowVip     string `json:"is_yellow_vip"`
	Vip             string `json:"vip"`
	YellowVipLevel  string `json:"yellow_vip_level"`
	Level           string `json:"level"`
	IsYellowYearVip string `json:"is_yellow_year_vip"`
}

// Login 登录
var Login = func(username, password string, loginType int, r *http.Request) (*SessionUser, string, error) {
	finder := zorm.NewFinder()
	finder.Append("SELECT * FROM ").Append(structs.TbUserAuthStructTableName)
	finder.Append("WHERE username=? AND login_type=?", username, loginType)
	authStruct := structs.TbUserAuthStruct{}
	row, err := zorm.QueryRow(context.Background(), finder, &authStruct)
	if !row && err != nil {
		panic(err)
	}
	err = bcrypt.CompareHashAndPassword([]byte(authStruct.Password), []byte(password))
	if err != nil {
		panic("密码不正确!")
	}
	finder = zorm.NewSelectFinder(structs.TbUserInfoStructTableName).Append("WHERE id=?", authStruct.UserInfoId)
	userinfostruct := structs.TbUserInfoStruct{}
	_, err = zorm.QueryRow(context.Background(), finder, &userinfostruct)
	if err != nil || userinfostruct.IsDisable == 1 {
		panic("用户信息不存在!")
	}
	claims := SessionUser{
		UserId:    strconv.Itoa(authStruct.UserInfoId),
		LoginCode: authStruct.Username,
		NickName:  userinfostruct.Nickname,
		Email:     userinfostruct.Email,
		Avatar:    userinfostruct.Avatar,
	}
	newJWT := NewJWT()
	token, err := newJWT.CreateToken(&claims)
	if err != nil {
		panic(err)
	}
	//更新最后一次登录时间
	authStruct.IpAddress, _ = GetIpAddress(r)
	source, err := GetIpSource(authStruct.IpAddress)
	if err == nil {
		authStruct.IpSource = source.Province
	}
	authStruct.LastLoginTime = time.Now()
	_, err = zorm.Transaction(context.Background(), func(ctx context.Context) (interface{}, error) {
		return zorm.Update(ctx, &authStruct)
	})
	if err != nil {
		panic(err)
	}
	return &claims, token, nil
}

// QQLoginRedirect 扣扣登录重定向
var QQLoginRedirect = func(writer http.ResponseWriter, r *http.Request) *R {
	redirectUri := r.URL.Query().Get("redirect_uri")
	if redirectUri == "" {
		redirectUri = Conf.QqLogin.DefaultRedirectUrl
	}
	state := rand.Intn(8999) + 1000 //生成1000-9999之间的随机数
	display := r.URL.Query().Get("display")
	url := fmt.Sprint("https://graph.qq.com/oauth2.0/authorize?response_type=code",
		"&client_id=", Conf.QqLogin.AppId,
		"&redirect_uri=", url.PathEscape(redirectUri),
		"&state=", state,
		"&scope=get_user_info",
		"&display=", display)
	http.Redirect(writer, r, url, http.StatusTemporaryRedirect)
	return Ok()
}

// QQLoginDefaultRedirectTarget 扣扣登录重定向目标获取用户信息
var QQLoginDefaultRedirectTarget = func(r *http.Request) (*SessionUser, string, error) {
	code := r.URL.Query().Get("code")
	state := r.URL.Query().Get("state")
	if state == "" {
		panic("state is empty")
	}
	token, open, qqUser, err := GetQQUserInfo(code)
	if err != nil {
		panic(err.Error())
	}
	_, err = func(username, password, code, nickname, avatar string) (interface{}, error) {
		return zorm.Transaction(context.Background(), func(ctx context.Context) (interface{}, error) {
			Setex(USER_CODE_KEY+username, []byte(code), CODE_EXPIRE_TIME)
			register := Register(username, password, code, QQ)
			if register.Code != 2000 {
				return nil, err
			}
			finder := zorm.NewSelectFinder(structs.TbUserInfoStructTableName).Append("WHERE id=?", register.Data.(int))
			var userInfoStruct structs.TbUserInfoStruct
			has, err := zorm.QueryRow(ctx, finder, &userInfoStruct)
			if err != nil || !has {
				return nil, err
			}
			userInfoStruct.Nickname = nickname
			userInfoStruct.Avatar = avatar
			return zorm.Update(ctx, &userInfoStruct)
		})
	}(open.Unionid, token.AccessToken, code, qqUser.Nickname, qqUser.FigureurlQq1)
	if err != nil {
		panic(err.Error())
	}
	return Login(open.Unionid, token.AccessToken, QQ, r)
}

// GetQQUserInfo 获取扣扣登录用户信息
var GetQQUserInfo = func(code string) (*tokenDTO, *openDTO, *qqUserInfo, error) {
	token, err := getQQLoginTokenDTO(code)
	if err != nil {
		return nil, nil, nil, err
	}
	open, err := getQQLoginOpenIdDTO(token)
	if err != nil {
		return token, nil, nil, err
	}
	info, err := getQQLoginUserInfo(open)
	if err != nil {
		return token, open, nil, err
	}
	return token, open, info, nil
}

// getQQLoginTokenDTO 获取扣扣登录token
var getQQLoginTokenDTO = func(code string) (*tokenDTO, error) {
	if code == "" {
		return nil, errors.New("code is empty")
	}
	url := fmt.Sprint("https://graph.qq.com/oauth2.0/token?grant_type=authorization_code",
		"&client_id=", Conf.QqLogin.AppId,
		"&client_secret=", Conf.QqLogin.AppKey,
		"&code=", code,
		"&redirect_uri=", url.PathEscape(Conf.QqLogin.DefaultRedirectUrl),
		"&fmt=json")
	get, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer get.Body.Close()

	data, err := ioutil.ReadAll(get.Body)
	if err != nil {
		return nil, err
	}
	var token tokenDTO
	err = json.Unmarshal(data, &token)
	if err != nil {
		return nil, err
	}
	return &token, nil
}

// getQQLoginOpenIdDTO 获取扣扣登录openId
var getQQLoginOpenIdDTO = func(token *tokenDTO) (*openDTO, error) {
	if token == nil {
		return nil, errors.New("token is nil")
	}
	url := fmt.Sprint("https://graph.qq.com/oauth2.0/me?access_token=", token.AccessToken,
		"&unionid=", Conf.QqLogin.HasUnionid,
		"&fmt=json")
	get, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer get.Body.Close()

	data, err := ioutil.ReadAll(get.Body)
	if err != nil {
		return nil, err
	}
	var open openDTO
	err = json.Unmarshal(data, &open)
	if err != nil {
		return nil, err
	}
	open.AccessToken = token.AccessToken
	return &open, nil
}

// getQQLoginUserInfo 获取扣扣登录用户信息
var getQQLoginUserInfo = func(open *openDTO) (*qqUserInfo, error) {
	url := fmt.Sprint("https://graph.qq.com/user/get_user_info?access_token=", open.AccessToken,
		"&oauth_consumer_key=", open.ClientId,
		"&openid=", open.Openid)
	get, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer get.Body.Close()

	data, err := ioutil.ReadAll(get.Body)
	var qqUser qqUserInfo
	err = json.Unmarshal(data, &qqUser)
	if err != nil {
		return nil, err
	}
	return &qqUser, nil
}
